﻿using System;
using System.Collections.Generic;
using System.Threading;
using CommonTypes.Server;
using CommonTypes.Server.Operations;
using Server.Model;
using Server.Services.Primary;
using Server.Services.Secondary;

namespace Server.Services
{
    public abstract class PingManager
    {
        public static void managePingProtocol( bool StartAsPrimary, AutoResetEvent ev, ServerData serverData )
        {
            PingManager controller;
            if( StartAsPrimary )
            {
                controller = new PrimaryPingController( );
            }
            else
            {
                controller = new SecondaryPingManager( );
            }
            AutoResetEvent autoEvent = new AutoResetEvent( false );
            KeyValuePair<AutoResetEvent, ServerData> argPair = new KeyValuePair<AutoResetEvent, ServerData>( autoEvent, serverData );
            Timer pingTimer = new Timer( controller.executePingTimer, argPair, 4000, 4000 );
            while( true )
            {
                if( ev.WaitOne( 200, false ) )
                {
                    serverData.State = ServerState.ADDREMOVE;
                    WaitHandle wait = new EventWaitHandle( false, EventResetMode.AutoReset );
                    pingTimer.Dispose( wait );
                    wait.WaitOne( );
                    serverData.IsTerminated.Set( );
                    return;
                }
                if( autoEvent.WaitOne( 200, false ) )
                {
                    swapServerRole( serverData, out controller, autoEvent, out argPair, ref pingTimer );
                }
            }
        }

        private static void swapServerRole( ServerData serverData, out PingManager controller, AutoResetEvent autoEvent, out KeyValuePair<AutoResetEvent, ServerData> argPair, ref Timer pingTimer )
        {
            System.Console.WriteLine( "Someone suspects from me or I suspect from someone so..." );
            serverData.State = ServerState.BULLYING;
            serverData.ServersInformation.PrimaryId = null;
            serverData.ServersInformation.Primary = false;
            bool isPrimary = BullyProtocol.startBully( serverData );

            if( !isPrimary )
            {
                WaitHandle wait = new EventWaitHandle( false, EventResetMode.AutoReset );
                pingTimer.Dispose( wait );

                serverData.ServersInformation.PrimaryId = BullyProtocol.obtainPrimary( serverData );
                serverData.ServersInformation.Primary = false;
                serverData.PingInformation.PingReceived = true;
                serverData.PingInformation.NumberOfTries = 0;

                foreach( String servers in serverData.ServersInformation.ServersLocation.Keys )
                {
                    if( servers != serverData.ServerId )
                        serverData.ServersInformation.ActiveServers[ servers ] = true;
                }

                System.Console.WriteLine( "I'm an secondary!" );
                controller = new SecondaryPingManager( );

                System.Console.WriteLine( "I'm going to start receiving pings..." );
                serverData.State = ServerState.ESTABLISHED;
                argPair = new KeyValuePair<AutoResetEvent, ServerData>( autoEvent, serverData );
                pingTimer = new Timer( controller.executePingTimer, argPair, 3600, 3600 );
            }
            else
            {
                WaitHandle wait = new EventWaitHandle( false, EventResetMode.AutoReset );
                pingTimer.Dispose( wait );

                serverData.ServersInformation.PrimaryId = serverData.ServerId;
                serverData.ServersInformation.Primary = true;

                System.Console.WriteLine( "I'm a primary!" );
                controller = new PrimaryPingController( );

                long[ ] tickets = new long[ 3 ];

                for( int n = 0; n < 3; n++ )
                {
                    tickets[ n ] = 0;
                }

                int i = 0;

                foreach( KeyValuePair<String, IServerToServer> servers in serverData.ServersInformation.ServersProxy )
                {
                    if( servers.Key != serverData.ServerId )
                    {
                        try
                        {
                            tickets[ i ] = servers.Value.sendCurrentTicket( ).TicketNumber;
                            System.Console.WriteLine( String.Format( "{0} ticket is {1}", servers.Key, tickets[ i ] ) );
                            i++;
                        }
                        catch
                        {
                            System.Console.WriteLine( "Server does not exist" );
                            serverData.ServersInformation.ActiveServers[ servers.Key ] = false;
                            continue;
                        }
                    }
                }

                argPair = new KeyValuePair<AutoResetEvent, ServerData>( autoEvent, serverData );
                pingTimer = new Timer( controller.executePingTimer, argPair, 4000, 4000 );

                Array.Sort( tickets );

                if( ( i != 0 ) && ( serverData.TicketNumber < tickets[ i ] ) )
                {
                    System.Console.WriteLine( String.Format( "My old ticket is {0}", serverData.TicketNumber ) );
                    System.Console.WriteLine( String.Format( "The new ticket is {0}", tickets[ 2 ] ) );
                    TicketGeneration gen = new TicketGeneration( serverData.ServerLogicalClock, tickets[ 2 ] );
                    serverData.Operations.Add( serverData.ServerLogicalClock, gen );
                    foreach( KeyValuePair<String, IServerToServer> servers in serverData.ServersInformation.ServersProxy )
                    {
                        if( servers.Key != serverData.ServerId && serverData.ServersInformation.ActiveServers[ servers.Key ] )
                        {
                            try
                            {
                                servers.Value.AcknowledgeTicket( gen.TicketNumber, "", gen.LogicalNumber );
                            }
                            catch
                            {
                                System.Console.WriteLine( "Server does not exist" );
                                serverData.ServersInformation.ActiveServers[ servers.Key ] = false;
                                continue;
                            }
                        }
                    }
                    serverData.ServerLogicalClock++;
                }
                System.Console.WriteLine( "Sync done!" );

                serverData.State = ServerState.ESTABLISHED;
            }
        }

        public abstract void executePingTimer( Object state );

        public static void terminatePingManager( ServerData server )
        {
            server.Terminate.Set( );
            server.IsTerminated.WaitOne( );
        }
    }
}